package com.campus.service.impl;

import com.alibaba.fastjson2.JSON;
import com.campus.common.RedisCache;
import com.campus.common.Result;
import com.campus.entity.PostDetailDTO;
import com.campus.entity.Posts;
import com.campus.mapper.HotRankingsMapper;
import com.campus.service.HotRankingService;
import com.campus.service.PostService;
import com.github.pagehelper.PageHelper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Service
public class HotRankingServiceImpl implements HotRankingService {
    @Autowired
    private HotRankingsMapper hotRankingsMapper;
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    @Autowired
    private PostService postService;

    /**
     * 总的热力帖子
     *
     * @param limit
     * @param date
     * @return
     */
    @Override
    public Result getHotPosts(int limit, String date) {
        PageHelper.clearPage();

        // 先尝试从Redis ZSet中获取数据
        String zsetKey = RedisCache.HOT_RANKING_ZSET_KEY_PREFIX + date;
        Set<ZSetOperations.TypedTuple<String>> typedTuples = stringRedisTemplate.opsForZSet()
                .reverseRangeWithScores(zsetKey, 0, limit - 1);

        List<PostDetailDTO> hotPosts = new ArrayList<>();
        if (typedTuples != null && !typedTuples.isEmpty()) {
            // 从Redis中获取到数据，根据postId查询帖子详情
            List<String> postIds = typedTuples.stream()
                    .map(ZSetOperations.TypedTuple::getValue)
                    .collect(Collectors.toList());

            // 从Redis缓存中获取帖子详情
            hotPosts = getPostDetailsFromRedis(postIds);
        } else {
            // Redis中没有数据，从数据库查询并存入Redis
            List<PostDetailDTO> hotPostsByDate = hotRankingsMapper.getHotPostsByDate(limit, date);
            if (hotPostsByDate == null || hotPostsByDate.isEmpty()) {
                return Result.ok("暂无热力帖子");
            }

            // 将数据存入Redis ZSet
            saveHotPostsToRedis(hotPostsByDate, date);

            // 将帖子详情也缓存到Redis中
            savePostDetailsToRedis(hotPostsByDate);

            hotPosts = hotPostsByDate;
        }

        // 返回包含rankings字段的对象，以匹配前端期望的数据结构
        return Result.ok(java.util.Map.of("rankings", hotPosts));
    }

    /**
     * 分类查询的热力帖子
     *
     * @param categoryId
     * @param limit
     * @param date
     * @return
     */
    @Override
    public Result getCategoryHotPosts(String categoryId, int limit, String date) {
        //清理一下分页查询缓存
        PageHelper.clearPage();
        List<PostDetailDTO> hotPostsByDate = hotRankingsMapper.getCategoryHotPostsByDate(categoryId, limit, date);
        if (hotPostsByDate == null) {
            return Result.ok("暂无该分类的热力帖子");
        }
        // 返回包含rankings字段的对象，以匹配前端期望的数据结构
        return Result.ok(java.util.Map.of("rankings", hotPostsByDate));
    }

    /**
     * 获取发布帖子的数量
     *
     * @return
     */
    @Override
    public Result getPostCount(String date, String categoryId) {
        String key = null;

        if (categoryId == null) {
            Set<String> keys = stringRedisTemplate.keys(date + RedisCache.POST_COUNT + "*");
            int num = 0;
            for (String k : keys) {
                num += Integer.parseInt(stringRedisTemplate.opsForValue().get(k));
            }
            return Result.ok("发布帖子数量", num);
        }
        key = date + RedisCache.POST_COUNT + categoryId;
        String num = stringRedisTemplate.opsForValue().get(key);
        if (num == null) {
            return Result.ok(0);
        }
        return Result.ok("发布帖子数量", num);
    }

    /**
     * 获取帖子浏览的数量
     *
     * @return
     */
    @Override
    public Result getPostViewCount(String date) {
        String key = date + RedisCache.POST_VIEW_COUNT;
        String num = stringRedisTemplate.opsForValue().get(key);
        if (num == null) {
            return Result.ok(0);
        }
        return Result.ok("帖子浏览数量", num);
    }

    /**
     * 获取热力榜redis中的key
     */
    public String getHotRankingKeyByDate(String date) {
        return "hot-ranking:" + date;
    }

    /**
     * 将热力帖子数据存入Redis ZSet
     *
     * @param hotPosts 热力帖子列表
     * @param date     时间范围
     */
    public void saveHotPostsToRedis(List<PostDetailDTO> hotPosts, String date) {
        String zsetKey = RedisCache.HOT_RANKING_ZSET_KEY_PREFIX + date;

        // 使用管道批量操作提高性能
        stringRedisTemplate.executePipelined((org.springframework.data.redis.core.RedisCallback<Object>) (connection) -> {
            for (PostDetailDTO post : hotPosts) {
                // 计算热度分数（可以根据业务需求调整算法）
                double score = calculateHotScore(post);
                connection.zAdd(zsetKey.getBytes(), score, post.getPostId().getBytes());
            }
            return null;
        });
    }

    /**
     * 计算帖子的热度分数
     * 简单的热度算法：点赞数*2 + 浏览量*1 + 评论数*3
     *
     * @param post 帖子详情
     * @return 热度分数
     */
    private double calculateHotScore(PostDetailDTO post) {
        int likesCount = post.getLikesCount() != null ? post.getLikesCount() : 0;
        int viewsCount = post.getViewsCount() != null ? post.getViewsCount() : 0;
        int commentsCount = post.getCommentsCount() != null ? post.getCommentsCount() : 0;

        return likesCount * 2 + viewsCount * 1 + commentsCount * 3;
    }

    /**
     * 更新帖子在ZSet中的分数
     * 当帖子的点赞数、浏览量或评论数发生变化时调用此方法
     *
     * @param postId        帖子ID
     * @param likesCount    点赞数
     * @param viewsCount    浏览量
     * @param commentsCount 评论数
     * @param date          时间范围（today, week, month）
     */
    public void updatePostScoreInZSet(String postId, int likesCount, int viewsCount, int commentsCount, String date) {
        String zsetKey = RedisCache.HOT_RANKING_ZSET_KEY_PREFIX + date;

        // 计算新的热度分数
        double score = likesCount * 2 + viewsCount * 1 + commentsCount * 3;

        // 更新ZSet中的分数
        stringRedisTemplate.opsForZSet().add(zsetKey, postId, score);
    }

    /**
     * 将帖子详情缓存到Redis中
     *
     * @param posts 帖子详情列表
     */
    public void savePostDetailsToRedis(List<PostDetailDTO> posts) {
        stringRedisTemplate.executePipelined((org.springframework.data.redis.core.RedisCallback<Object>) (connection) -> {
            for (PostDetailDTO post : posts) {
                String postKey = RedisCache.HOT_RANKING_POST_KEY_PREFIX + post.getPostId();
                String postJson = JSON.toJSONString(post);
                connection.set(postKey.getBytes(), postJson.getBytes());
                // 设置过期时间，例如1小时
                connection.expire(postKey.getBytes(), TimeUnit.HOURS.toSeconds(1));
            }
            return null;
        });
    }

    /**
     * 从Redis中获取帖子详情
     *
     * @param postIds 帖子ID列表
     * @return 帖子详情列表
     */
    private List<PostDetailDTO> getPostDetailsFromRedis(List<String> postIds) {
        List<PostDetailDTO> postDetails = new ArrayList<>();

        // 使用管道批量获取
        List<Object> results = stringRedisTemplate.executePipelined((org.springframework.data.redis.core.RedisCallback<Object>) (connection) -> {
            for (String postId : postIds) {
                String postKey = RedisCache.HOT_RANKING_POST_KEY_PREFIX + postId;
                connection.get(postKey.getBytes());
            }
            return null;
        });

        // 处理结果
        for (int i = 0; i < postIds.size(); i++) {
            String postJson = (String) results.get(i);
            if (StringUtils.hasText(postJson)) {
                // 从Redis缓存中获取到数据
                PostDetailDTO postDetail = JSON.parseObject(postJson, PostDetailDTO.class);
                postDetails.add(postDetail);
            } else {
                // Redis中没有数据，从数据库查询
                String postId = postIds.get(i);
                Result result = postService.getPostDetailById(postId);
                if (result.getCode() == Result.CODE_200 && result.getData() != null) {
                    PostDetailDTO postDetail = (PostDetailDTO) result.getData();
                    postDetails.add(postDetail);

                    // 将查询到的数据存入Redis
                    String postKey = RedisCache.HOT_RANKING_POST_KEY_PREFIX + postId;
                    String postJsonStr = JSON.toJSONString(postDetail);
                    stringRedisTemplate.opsForValue().set(postKey, postJsonStr, 1, TimeUnit.HOURS);
                }
            }
        }

        return postDetails;
    }
}